/**
 * @file common_thread_monitor.c
 * @author Jio (hedajun@hzdusun.com)
 * @brief 
 * @version 0.1
 * @date 2023-12-08
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#include <pthread.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <sys/types.h>  
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>  
#include <netinet/in.h> 
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "common_thread_monitor.h"
#include "common_dbg_log.h"


#define HS_KEEPDORR_WRITE_FILE         "/tmp/hskd_w_fifo"
#define HS_KEEPDORR_READ_FILE		   "/tmp/hskd_r_fifo"

typedef struct
{
    int wt_fd;
}thread_monitor_kd_t;




typedef struct 
{
    thread_monitor_context_t* ref_context;
    uint64_t last_check_schedule_times;
}thread_monitor_item_t;


thread_monitor_kd_t g_tm_kd = {};
thread_monitor_item_t g_tm_items[MAX_THREAD_MONITOR_COUNT] = {};
RB_INT32 g_tm_items_cnt = 0;
thread_catch_abort_callback_t g_thread_monitor_catch_abort_callback = NULL;
thread_monitor_sim_t g_tm_sim = TM_SIM_NONE;
static RB_INT32 thread_monitor_kdinit();
/**
 * @brief fifo通信，无电气干扰,判断报文头尾即可
 * 
 * @param heartbeat 
 * @param len 
 * @return RB_BOOL 
 */
RB_BOOL _is_vaild_heartbeat(uint8_t* heartbeat, uint8_t len)
{
	if ( (heartbeat[0] == 0xEA)
		&& (heartbeat[1] == 0x61)
		&& (heartbeat[len-1] == 0xEB)
		&& (heartbeat[len-2] == 0x62) )
	{
		return RB_TRUE;
	}

	return RB_FALSE;
}

RB_INT32 thread_monitor_kd_process(int fd) 
{
    int revLen;
    uint8_t heartBeatBuffer[255];
    static uint8_t heartbeat[] = {0xEA,0x61,0x00,0x00,0x14,0x00,0x00,0x04,0x01,0x00,0x04,0x00,0x62,0xEB};
    static uint8_t heartbeat_res[] = {0xEA,0x61,0x00,0x00,0x0E,0x00,0x00,0x04,0x02,0x00,0x04,0x60,0x62,0xEB};

    revLen = read(fd, heartBeatBuffer, 255);
    if( (revLen == sizeof(heartbeat) ) 
		&& (_is_vaild_heartbeat(heartBeatBuffer, revLen))) 
    {
		if(write(g_tm_kd.wt_fd, heartbeat_res, sizeof(heartbeat_res)) != sizeof(heartbeat_res))
        {
            return RB_ERR;
        }
	}

    return RB_OK;
}

RB_VOID* thread_monitor_routine(RB_VOID *arg)
{
    int rdFd = open(HS_KEEPDORR_WRITE_FILE, O_RDONLY | O_NONBLOCK);
	if (rdFd < 0) {
		DBG_LOG_ERR("open write fifo faild.");
	} else {
        if (thread_monitor_kdinit() == RB_OK) {
            DBG_LOG_INFO("keepdoor working");
        } else {
            close(rdFd);
        }
    }

    while(1) {
        fd_set fds;

        for (RB_INT32 i = 0; i < g_tm_items_cnt; i++)
        {
            // should not be happen 
            if (g_tm_items[i].ref_context == NULL)
                continue;

            thread_monitor_context_t* context = g_tm_items[i].ref_context;
            struct timespec now;
            clock_gettime(CLOCK_MONOTONIC, &now);
            RB_UINT64 deltaTime = 1000000L * (now.tv_sec - context->last_active_time.tv_sec ) 
                                + (now.tv_nsec - context->last_active_time.tv_nsec)/1000;

            // printf("name %s, schedule_times %llu, work_time_ticks %llu, watch_timeout_us %llu, delta time %llu\n", 
            //                                             context->name, 
            //                                             context->schedule_times, 
            //                                             context->work_time_ticks, 
            //                                             context->watch_timeout_us,
            //                                             deltaTime);

            if ( (g_tm_items[i].last_check_schedule_times == context->schedule_times)
                    && (context->watch_timeout_us != 0)
                    && (deltaTime > context->watch_timeout_us) ) {

                if (g_thread_monitor_catch_abort_callback != NULL) {
                    (*g_thread_monitor_catch_abort_callback)(context, "Block too long time");
                }

            }

            g_tm_items[i].last_check_schedule_times = context->schedule_times;
        }

        if ( (rdFd == -1) 
            || (g_tm_kd.wt_fd <= 0) ) {
            sleep(5);
        } else {
            FD_ZERO(&fds);
            FD_SET(rdFd, &fds);
            struct timeval timeout;
            timeout.tv_sec = 5;
            int ret = select(rdFd+1, &fds, NULL, NULL, &timeout);
            if (ret > 0) {
                if(FD_ISSET(rdFd, &fds)) {
                    thread_monitor_kd_process(rdFd);
                }
            } else if (ret < 0)  {
                close(rdFd);
                close(g_tm_kd.wt_fd);
                break;
            }
        }
    }

    return NULL;
}


/**
 * @brief 监听看门狗线程
 * 
 * @return RB_INT32 
 */
static RB_INT32 thread_monitor_kdinit()
{
    int rep;
    int fd = -1;
    struct timespec now;
    struct timespec start;
    clock_gettime(CLOCK_MONOTONIC, &start);

    do {
        clock_gettime(CLOCK_MONOTONIC, &now);
        RB_UINT64 deltaTime = 1000000L * (now.tv_sec - start.tv_sec ) 
                            + (now.tv_nsec - start.tv_nsec)/1000;

        if (deltaTime > 3*1000*1000) {
            break;
        }

        fd = open(HS_KEEPDORR_READ_FILE, O_WRONLY | O_NONBLOCK);
        if (fd > 0) {
            break;
        }

    }while(1);

    if (fd <= 0) {
        DBG_LOG_ERR("open read fifo failed [%d] error: %s(errno: %d)", fd, strerror(errno),errno);
        return RB_ERR;
    }

    g_tm_kd.wt_fd = fd;
    return RB_OK;
}
/**
 * @brief 启动线程监控
 * 
 * @return RB_INT32 
 */
RB_INT32 thread_monitor_init(thread_catch_abort_callback_t on_catch_abort_cb)
{
    pthread_t tid;

    g_thread_monitor_catch_abort_callback = on_catch_abort_cb;
    return pthread_create(&tid, NULL, thread_monitor_routine, NULL);
}

/**
 * @brief 将contetx加入线程监控
 * 
 * @param contex 
 * @return RB_INT32 
 */
RB_INT32 thread_monitor_catch(thread_monitor_context_t* context)
{
    assert(context != NULL);

    if (g_tm_items_cnt > MAX_THREAD_MONITOR_COUNT)
        return -1;

    g_tm_items[g_tm_items_cnt].ref_context =  context;
    g_tm_items[g_tm_items_cnt].last_check_schedule_times = 0;
    g_tm_items_cnt++;
    return 0;
}


/**
 * @brief 创建线程监控context
 * 
 * @param name 
 * @return thread_monitor_context_t* 
 */
thread_monitor_context_t* thread_monitor_create(const char* name)
{
    thread_monitor_context_t* context = NULL;

    context = (thread_monitor_context_t*)malloc(sizeof(thread_monitor_context_t));
    if (context)
    {
        memset(context, 0, sizeof(thread_monitor_context_t));
        strncpy(context->name, name, sizeof(context->name));
        clock_gettime(CLOCK_MONOTONIC, &context->last_active_time);
        context->schedule_times = 0;
        context->watch_timeout_us = 0;
        context->set_block = RB_FALSE;
    }

    return context;
}

/**
 * @brief 销毁线程监控context
 * 
 * @param name 
 * @return thread_monitor_context_t* 
 */
RB_VOID thread_monitor_destory(thread_monitor_context_t* context)
{
    if (context)
    {
        free(context);
    }
}

RB_VOID thread_monitor_context_active(thread_monitor_context_t* context)
{
    if (context)
    {
        // use for block test
        while (context->set_block == RB_TRUE)
        {
            sleep(1);      
        }

        clock_gettime(CLOCK_MONOTONIC, &context->last_active_time);
        context->schedule_times++;
    }
}

RB_VOID thread_monitor_context_update(thread_monitor_context_t* context)
{
    if (context)
    {
        struct timespec now;
        clock_gettime(CLOCK_MONOTONIC, &now);
        uint64_t deltaTime = 1000000L * (now.tv_sec - context->last_active_time.tv_sec ) + (now.tv_nsec - context->last_active_time.tv_nsec)/1000;   
        context->work_time_ticks += (deltaTime <= 0) ? 1 : deltaTime;
    }
}


RB_INT32 thread_monitor_iter(thread_monitor_iter_callback_t cb)
{
    assert(cb != NULL);
    for (RB_INT32 i = 0; i < g_tm_items_cnt; i++)
    {
        // should not be happen 
        if (g_tm_items[i].ref_context == NULL)
            continue;

        thread_monitor_context_t* context = g_tm_items[i].ref_context;
        (*cb)(context);
    }
}


RB_INT32 thread_monitor_block(RB_INT32 block_index, RB_BOOL block)
{
    if (block_index >= g_tm_items_cnt)
        return RB_ERR;

    thread_monitor_context_t* context = g_tm_items[block_index].ref_context;
    // should not be happen 
    if (context == NULL)
        return RB_ERR;

    context->set_block = block;
    return RB_OK;
}


RB_VOID thread_monitor_timeout_disable(thread_monitor_context_t* context)
{
    if (context)
        context->watch_timeout_us = 0;
}


RB_VOID thread_monitor_timeout_set(thread_monitor_context_t* context, RB_UINT64 timeout)
{
    if (context)
        context->watch_timeout_us = timeout;
}